/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dal/allocator.h"
#include "dal/config_loader.h"
#include "dal_cyclonedds/converter/StatusConverter.hpp"
#include "dal_cyclonedds/converter/StatusMaskConverter.hpp"
#include "dal_cyclonedds/converter/QosConverter.hpp"
#include "dal_cyclonedds/converter/RetTypeConverter.hpp"
#include "dal_cyclonedds/converter/InstanceHandleConverter.hpp"
#include "dal_cyclonedds/converter/GuidConverter.hpp"
#include "dal_cyclonedds/listener_impl/PublisherListenerImpl.hpp"
#include "dal_cyclonedds/listener_impl/DataWriterListenerImpl.hpp"
#include "dal_cyclonedds/utils/log.hpp"
#include "dal_cyclonedds/utils/EntityGetter.hpp"
#include "dal_cyclonedds/utils/TopicSertypeManager.hpp"

const dal_instance_handle_t DAL_HANDLE_NIL = {0, 0, 0, 0};

// PUBLISHER
dal_publisher_listener_t*
dal_create_publisher_listener(dal_publisher_listener_callbacks_t callbacks)
{
    dal_publisher_listener_t* listener = dal_publisher_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = new PublisherListenerImpl(callbacks);
    if (!listener->handle_) {
        LOG_ERROR() << "PublisherListenerImpl memory allocation failed." << LOG_ENDL;
        dal_publisher_listener_free(listener);
        return nullptr;
    }

    return listener;
}

dal_ret_t dal_delete_publisher_listener(dal_publisher_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    PublisherListenerImpl* listener_impl =
        reinterpret_cast<PublisherListenerImpl*>(listener->handle_);
    if (listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete publisher listener because it's entity still exist."
                    << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete listener_impl;
    dal_publisher_listener_free(listener);
    reinterpret_cast<dal_publisher_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_publisher_t* dal_domain_participant_create_publisher(dal_domain_participant_t* participant,
                                                         dal_publisher_qos_t* qos,
                                                         dal_publisher_listener_t* listener,
                                                         dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;
    dds_entity_t c_participant = get_cyclone_domain_participant(participant);
    if (c_participant <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone domain participant failed." << LOG_ENDL;
        return nullptr;
    }

    dal_publisher_t* d_pub = dal_publisher_allocate();
    if (!d_pub) {
        LOG_ERROR_WITH_LOCATION() << "dal publisher allocate failed." << LOG_ENDL;
        return nullptr;
    }

    PublisherListenerImpl* c_listener_impl =
        listener ? reinterpret_cast<PublisherListenerImpl*>(listener->handle_) : nullptr;
    dds_listener_t* c_listener = NULL;
    if (c_listener_impl != nullptr) {
        c_listener = c_listener_impl->CycloneDDSListener();
    }

    dds_qos_t* c_qos = dds_create_qos();
    d_ret = QosConverter::GetInstance()->ConvertPublisherQosFromDalToCyclonedds(qos, c_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert publisher qos failed." << LOG_ENDL;
        dal_publisher_free(d_pub);
        dds_delete_qos(c_qos);
        return nullptr;
    }

    dds_entity_t c_pub = dds_create_publisher(c_participant, c_qos, c_listener);
    dds_delete_qos(c_qos);
    if (c_pub < 0) {
        LOG_ERROR_WITH_LOCATION() << "create publisher failed." << LOG_ENDL;
        dal_publisher_free(d_pub);
        return nullptr;
    }

    d_pub->handle_ = new DalPublisherImpl(c_pub, c_listener_impl);

    uint32_t c_mask = 0;
    StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToCyclonedds(mask, &c_mask);
    StatusMaskConverter::GetInstance()->FilterPublisherStatusMask(&c_mask);
    if (dds_set_status_mask(c_pub, c_mask) != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "set publisher status mask failed." << LOG_ENDL;
        return d_pub;
    }

    TopicSertypeManager::GetInstance()->AddEntityToParticipant(c_participant, c_pub);

    return d_pub;
}

dal_publisher_t*
dal_domain_participant_create_publisher_with_profile(dal_domain_participant_t* participant,
                                                     dal_publisher_qos_t* qos,
                                                     const char* profile_name,
                                                     dal_publisher_listener_t* listener,
                                                     dal_status_mask_t mask)
{
    return dal_domain_participant_create_publisher(participant, qos, listener, mask);
}

dal_ret_t dal_domain_participant_delete_publisher(dal_domain_participant_t* participant,
                                                  dal_publisher_t* pub)
{
    dds_return_t c_ret;
    dds_entity_t c_participant = get_cyclone_domain_participant(participant);
    if (c_participant <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dds_entity_t c_pub = get_cyclone_publisher(pub);
    if (c_pub <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone publisher failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (c_participant != dds_get_parent(c_pub)) {
        LOG_ERROR_WITH_LOCATION() << "publisher is not belong to participant." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    const int MAX_CHILDREN_NUM = 100;
    dds_entity_t* children = (dds_entity_t*)malloc(sizeof(dds_entity_t) * MAX_CHILDREN_NUM);
    c_ret = dds_get_children(c_pub, children, MAX_CHILDREN_NUM);
    if (c_ret > 0) {
        LOG_ERROR_WITH_LOCATION() << "publisher still has children." << LOG_ENDL;
        free(children);
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }
    free(children);

    c_ret = dds_delete(c_pub);
    dal_ret_t d_ret = RetTypeConverter::GetInstance()->ConvertRetFromCycloneddsToDal(c_ret);
    if (d_ret != DAL_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "delete publisher failed." << LOG_ENDL;
        return d_ret;
    }

    auto pub_impl = reinterpret_cast<DalPublisherImpl*>(pub->handle_);
    delete pub_impl;
    dal_publisher_free(pub);
    reinterpret_cast<dal_publisher_t*>(pub)->handle_ = nullptr;

    TopicSertypeManager::GetInstance()->RemoveEntityFromParticipant(c_participant, c_pub);

    return DAL_RETCODE_OK;
}

// DATAWRITER
dal_datawriter_listener_t*
dal_create_datawriter_listener(dal_datawriter_listener_callbacks_t callbacks)
{
    dal_datawriter_listener_t* listener = dal_datawriter_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = new DataWriterListenerImpl(callbacks);
    if (!listener->handle_) {
        LOG_ERROR() << "DataWriterListenerImpl memory allocation failed." << LOG_ENDL;
        dal_datawriter_listener_free(listener);
        return nullptr;
    }

    return listener;
}

dal_ret_t dal_delete_datawriter_listener(dal_datawriter_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DataWriterListenerImpl* listener_impl =
        reinterpret_cast<DataWriterListenerImpl*>(listener->handle_);
    if (listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete datawriter listener because it's entity still exist."
                    << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete listener_impl;
    dal_datawriter_listener_free(listener);
    reinterpret_cast<dal_datawriter_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_datawriter_t* dal_publisher_create_datawriter(dal_publisher_t* pub,
                                                  dal_topic_t* topic,
                                                  dal_datawriter_qos_t* qos,
                                                  dal_datawriter_listener_t* listener,
                                                  dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    dds_entity_t c_pub = get_cyclone_publisher(pub);
    if (c_pub <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone publisher failed." << LOG_ENDL;
        return nullptr;
    }

    dds_entity_t c_topic = get_cyclone_topic(topic);
    if (c_topic <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone topic failed." << LOG_ENDL;
        return nullptr;
    }

    dal_datawriter_t* d_writer = dal_datawriter_allocate();
    if (!d_writer) {
        LOG_ERROR_WITH_LOCATION() << "dal datawriter allocate failed." << LOG_ENDL;
        return nullptr;
    }

    DataWriterListenerImpl* c_listener_impl =
        listener ? reinterpret_cast<DataWriterListenerImpl*>(listener->handle_) : nullptr;
    dds_listener_t* c_listener = NULL;
    if (c_listener_impl != nullptr) {
        c_listener = c_listener_impl->CycloneDDSListener();
    }

    dds_qos_t* c_qos = dds_create_qos();
    d_ret = QosConverter::GetInstance()->ConvertDataWriterQosFromDalToCyclonedds(qos, c_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert datawriter qos failed." << LOG_ENDL;
        dal_datawriter_free(d_writer);
        dds_delete_qos(c_qos);
        return nullptr;
    }

    dds_entity_t c_writer = dds_create_writer(c_pub, c_topic, c_qos, c_listener);
    dds_delete_qos(c_qos);
    if (c_writer < 0) {
        LOG_ERROR_WITH_LOCATION() << "create datawriter failed." << LOG_ENDL;
        dal_datawriter_free(d_writer);
        return nullptr;
    }

    d_writer->handle_ = new DalDataWriterImpl(c_writer, c_listener_impl);

    uint32_t c_mask = 0;
    StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToCyclonedds(mask, &c_mask);
    StatusMaskConverter::GetInstance()->FilterDataWriterStatusMask(&c_mask);
    if (dds_set_status_mask(c_writer, c_mask) != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "set datawriter status mask failed." << LOG_ENDL;
        return d_writer;
    }

    return d_writer;
}

dal_datawriter_t* dal_publisher_create_datawriter_with_profile(dal_publisher_t* pub,
                                                               dal_topic_t* topic,
                                                               dal_datawriter_qos_t* qos,
                                                               const char* profile_name,
                                                               dal_datawriter_listener_t* listener,
                                                               dal_status_mask_t mask)
{
    return dal_publisher_create_datawriter(pub, topic, qos, listener, mask);
}

dal_ret_t dal_publisher_delete_datawriter(dal_publisher_t* pub, dal_datawriter_t* writer)
{
    dds_entity_t c_pub = get_cyclone_publisher(pub);
    if (c_pub <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone publisher failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dds_entity_t c_writer = get_cyclone_datawriter(writer);
    if (c_writer <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone datawriter failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (c_pub != dds_get_parent(c_writer)) {
        LOG_ERROR_WITH_LOCATION() << "datawriter is not belong to publisher." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dds_return_t c_ret = dds_delete(c_writer);
    dal_ret_t d_ret = RetTypeConverter::GetInstance()->ConvertRetFromCycloneddsToDal(c_ret);
    if (d_ret != DAL_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "delete datawriter failed." << LOG_ENDL;
        return d_ret;
    }

    auto writer_impl = reinterpret_cast<DalDataWriterImpl*>(writer->handle_);
    delete writer_impl;
    dal_datawriter_free(writer);
    reinterpret_cast<dal_datawriter_t*>(writer)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_ret_t dal_datawriter_write(dal_datawriter_t* writer,
                               dal_data data,
                               dal_instance_handle_t handle = DAL_HANDLE_NIL)
{
    dds_instance_handle_t c_handle;

    dds_entity_t c_writer = get_cyclone_datawriter(writer);
    if (c_writer <= 0) {
        LOG_ERROR_WITH_LOCATION() << "get cyclone datawriter failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dal_ret_t d_ret =
        InstanceHandleConverter::GetInstance()->ConvertInstanceHandleFromDalToCyclonedds(&handle,
                                                                                         &c_handle);
    if (DAL_RETCODE_OK != d_ret)
        return d_ret;

    c_writer =
        reinterpret_cast<DalDataWriterImpl*>(writer->handle_)->CycloneddsDataWriter();
    if (DDS_RETCODE_OK != dds_write(c_writer, data)) {
        return DAL_RETCODE_ERROR;
    }
    return DAL_RETCODE_OK;
}

dal_guid_t dal_datawriter_get_guid(dal_datawriter_t* writer)
{
    dal_guid_t d_guid = { };
    dds_entity_t c_writer =
        reinterpret_cast<DalDataWriterImpl*>(writer->handle_)->CycloneddsDataWriter();
    dds_guid_t c_guid;
    if (dds_get_guid(c_writer, &c_guid) != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "get guid by entity failed." << LOG_ENDL;
        return d_guid;
    }
    ddsi_guid_t c_guid_s;
    memcpy(&c_guid_s, &c_guid, sizeof(dds_guid_t));
    GuidConverter::GetInstance()->ConvertGuidFromCycloneddsToDal(&c_guid_s, &d_guid);
    return d_guid;
}
